/*
GNU getopt for MSDOS and Microsoft C.

Enclosed:

	*  README		This file (the appetizer).
	*  GETOPT.C		The main course.
	*  MAKEFILE		The dessert, an ndmake-file
				  that makes libraries and a test
				  program.

The MSDOS-specific capabilities are invoked
by defining MSDOS_FLAGS at compile time.
In particular, "/" is allowed as a synonym
for "-" and "/-" as a synonym for "--".

I chose to hack the GNU getopt after hacking
the AT&T getopt and realising that it wouldn't
allow for "/" style flags after non-option arguments--
not very MSDOS-like behaviour.

	-- Barry Schwartz, Dec. 1989
*/

/*
 * GNU getopt, hacked for MSDOS and Microsoft C 5.1 and QuickC.
 *
 * Barry Schwartz, Dec. 1989
 *
 * New compile-time defs:
 *   MSDOS			Defined by Microsoft compilers
 *   MSDOS_FLAGS		Allow "/" as a synonym for "-"
 *				and "/-" as a synonym for "--"
 */

#define MSDOS_FLAGS

/*
 * This version of `getopt' appears to the caller like standard Unix
 * `getopt' but it behaves differently for the user, since it allows the
 * user to intersperse the options with the other arguments.
 *
 * As `getopt' works, it permutes the elements of `argv' so that, when it is
 * done, all the options precede everything else.  Thus all application
 * programs are extended to handle flexible argument order.
 *
 * Setting the environment variable _POSIX_OPTION_ORDER disables permutation.
 * Then the behavior is completely standard.
 *
 * GNU application programs can use a third alternative mode in which they
 * can distinguish the relative order of options and other arguments.
 */

#include <stdio.h>
#if defined(MSDOS)
#include <malloc.h>
#include <string.h>
extern char *getenv(char *);
#endif

#if defined(sparc)
#include <alloca.h>
#endif
#if defined(USG) || defined(MSDOS)
#define bcopy(s, d, l)	memcpy((d), (s), (l))
#define index		strchr
#endif

/*
 * For communication from `getopt' to the caller. When `getopt' finds an
 * option that takes an argument, the argument value is returned here.
 * Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element
 * is returned here.
 */

char           *optarg = 0;

/*
 * Index in ARGV of the next element to be scanned. This is used for
 * communication to and from the caller and for communication between
 * successive calls to `getopt'.
 *
 * On entry to `getopt', zero means this is the first call; initialize.
 *
 * When `getopt' returns EOF, this is the index of the first of the
 * non-option elements that the caller should itself scan.
 *
 * Otherwise, `optind' communicates from one call to the next how much of
 * ARGV has been scanned so far.
 */

int             optind = 0;

/*
 * The next char to be scanned in the option-element in which the last
 * option character we returned was found. This allows us to pick up the
 * scan where we left off.
 *
 * If this is zero, or a null string, it means resume the scan by advancing
 * to the next ARGV-element.
 */

static char    *nextchar;

/*
 * Callers store zero here to inhibit the error message for unrecognized
 * options.
 */

int             opterr = 1;

/*
 * Describe how to deal with options that follow non-option ARGV-elements.
 *
 * UNSPECIFIED means the caller did not specify anything; the default is then
 * REQUIRE_ORDER if the environment variable _OPTIONS_FIRST is defined,
 * PERMUTE otherwise.
 *
 * REQUIRE_ORDER means don't recognize them as options. Stop option
 * processing when the first non-option is seen. This is what Unix does.
 *
 * PERMUTE is the default.  We permute the contents of `argv' as we scan, so
 * that eventually all the options are at the end.  This allows options to
 * be given in any order, even with programs that were not written to
 * expect this.
 *
 * RETURN_IN_ORDER is an option available to programs that were written to
 * expect options and other ARGV-elements in any order and that care about
 * the ordering of the two.  We describe each non-option ARGV-element as
 * if it were the argument of an option with character code zero. Using
 * `-' as the first character of the list of option characters requests
 * this mode of operation.
 *
 * The special argument `--' forces an end of option-scanning regardless of
 * the value of `ordering'.  In the case of RETURN_IN_ORDER, only `--' can
 * cause `getopt' to return EOF with `optind' != ARGC.
 */

static enum
{
    REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
}               ordering;


/* Handle permutation of arguments.  */

/*
 * Describe the part of ARGV that contains non-options that have been
 * skipped.  `first_nonopt' is the index in ARGV of the first of them;
 * `last_nonopt' is the index after the last of them.
 */

static int      first_nonopt;
static int      last_nonopt;

/*
 * Exchange two adjacent subsequences of ARGV. One subsequence is elements
 * [first_nonopt,last_nonopt) which contains all the non-options that have
 * been skipped so far. The other is elements [last_nonopt,optind), which
 * contains all the options processed since those non-options were
 * skipped.
 *
 * `first_nonopt' and `last_nonopt' are relocated so that they describe the
 * new indices of the non-options in ARGV after they are moved.
 */

static void exchange(char **argv)
{
    int             nonopts_size
    = (last_nonopt - first_nonopt) * sizeof(char *);
    char          **temp = (char **) alloca(nonopts_size);

    /* Interchange the two blocks of data in argv.  */

    bcopy(&argv[first_nonopt], temp, nonopts_size);
    bcopy(&argv[last_nonopt], &argv[first_nonopt],
	  (optind - last_nonopt) * sizeof(char *));
    bcopy(temp, &argv[first_nonopt + optind - last_nonopt],
	  nonopts_size);

    /* Update records for the slots the non-options now occupy.  */

    first_nonopt += (optind - last_nonopt);
    last_nonopt = optind;
}


/*
 * Scan elements of ARGV (whose length is ARGC) for option characters
 * given in OPTSTRING.
 *
 * If an element of ARGV starts with '-', and is not exactly "-" or "--",
 * then it is an option element.  The characters of this element (aside
 * from the initial '-') are option characters.  If `getopt' is called
 * repeatedly, it returns successively each of theoption characters from
 * each of the option elements.
 *
 * If `getopt' finds another option character, it returns that character,
 * updating `optind' and `nextchar' so that the next call to `getopt' can
 * resume the scan with the following option character or ARGV-element.
 *
 * If there are no more option characters, `getopt' returns `EOF'. Then
 * `optind' is the index in ARGV of the first ARGV-element that is not an
 * option.  (The ARGV-elements have been permuted so that those that are
 * not options now come last.)
 *
 * OPTSTRING is a string containing the legitimate option characters. A colon
 * in OPTSTRING means that the previous character is an option that wants
 * an argument.  The argument is taken from the rest of the current
 * ARGV-element, or from the following ARGV-element, and returned in
 * `optarg'.
 *
 * If an option character is seen that is not listed in OPTSTRING, return '?'
 * after printing an error message.  If you set `opterr' to zero, the
 * error message is suppressed but we still return '?'.
 *
 * If a char in OPTSTRING is followed by a colon, that means it wants an arg,
 * so the following text in the same ARGV-element, or the text of the
 * following ARGV-element, is returned in `optarg.  Two colons mean an
 * option that wants an optional arg; if there is text in the current
 * ARGV-element, it is returned in `optarg'.
 *
 * If OPTSTRING starts with `-', it requests a different method of handling
 * the non-option ARGV-elements.  See the comments about RETURN_IN_ORDER,
 * above.
 */

int getopt(int argc, char **argv, char *optstring)
{
    /*
     * Initialize the internal data when the first call is made. Start
     * processing options with ARGV-element 1 (since ARGV-element 0 is the
     * program name); the sequence of previously skipped non-option
     * ARGV-elements is empty.
     */

    if (optind == 0)
    {
	first_nonopt = last_nonopt = optind = 1;

	nextchar = 0;

	/*
	 * Determine how to handle the ordering of options and nonoptions.
	 */

	if (optstring[0] == '-')
	    ordering = RETURN_IN_ORDER;
	else
	if (getenv("_POSIX_OPTION_ORDER") != 0)
	    ordering = REQUIRE_ORDER;
	else
	    ordering = PERMUTE;
    }

    if (nextchar == 0 || *nextchar == 0)
    {
	if (ordering == PERMUTE)
	{
	    /*
	     * If we have just processed some options following some
	     * non-options, exchange them so that the options come first.
	     */

	    if (first_nonopt != last_nonopt && last_nonopt != optind)
		exchange(argv);
	    else
	    if (last_nonopt != optind)
		first_nonopt = optind;

	    /*
	     * Now skip any additional non-options and extend the range of
	     * non-options previously skipped.
	     */

	    while (optind < argc
#if !defined(MSDOS_FLAGS)
		   && (argv[optind][0] != '-'
#else
		   && ((argv[optind][0] != '-' && argv[optind][0] != '/')
#endif
		       || argv[optind][1] == 0))
		optind++;
	    last_nonopt = optind;
	}

	/*
	 * Special ARGV-element `--' means premature end of options. Skip
	 * it like a null option, then exchange with previous non-options
	 * as if it were an option, then skip everything else like a
	 * non-option.
	 */

#if !defined(MSDOS_FLAGS)
	if (optind != argc && !strcmp(argv[optind], "--"))
#else
	if (optind != argc &&
	    !(strcmp(argv[optind], "--") && strcmp(argv[optind], "/-")))
#endif
	{
	    optind++;

	    if (first_nonopt != last_nonopt && last_nonopt != optind)
		exchange(argv);
	    else
	    if (first_nonopt == last_nonopt)
		first_nonopt = optind;
	    last_nonopt = argc;

	    optind = argc;
	}

	/*
	 * If we have done all the ARGV-elements, stop the scan and back
	 * over any non-options that we skipped and permuted.
	 */

	if (optind == argc)
	{
	    /*
	     * Set the next-arg-index to point at the non-options that we
	     * previously skipped, so the caller will digest them.
	     */
	    if (first_nonopt != last_nonopt)
		optind = first_nonopt;
	    return EOF;
	}

	/*
	 * If we have come to a non-option and did not permute it, either
	 * stop the scan or describe it to the caller and pass it by.
	 */

#if !defined(MSDOS_FLAGS)
	if (argv[optind][0] != '-' || argv[optind][1] == 0)
#else
	if ((argv[optind][0] != '-' && argv[optind][0] != '/')
	    || argv[optind][1] == 0)
#endif
	{
	    if (ordering == REQUIRE_ORDER)
		return EOF;
	    optarg = argv[optind++];
	    return 0;
	}

	/*
	 * We have found another option-ARGV-element. Start decoding its
	 * characters.
	 */

	nextchar = argv[optind] + 1;
    }

    /* Look at and handle the next option-character.  */

    {
	char            c = *nextchar++;
	char           *temp = (char *) index(optstring, c);

	/*
	 * Increment `optind' when we start to process its last character.
	 */
	if (*nextchar == 0)
	    optind++;

	if (temp == 0 || c == ':')
	{
	    if (opterr != 0)
	    {
		if (c < 040 || c >= 0177)
                    fprintf(stderr, "\n%s: unrecognized option, character code 0%o\n",
			    argv[0], c);
		else
                    fprintf(stderr, "\n%s: unrecognized option `-%c'\n",
			    argv[0], c);
	    }
	    return '?';
	}
	if (temp[1] == ':')
	{
	    if (temp[2] == ':')
	    {
		/*
		 * This is an option that accepts an argument optionally.
		 */
		if (*nextchar != 0)
		{
		    optarg = nextchar;
		    optind++;
		}
		else
		    optarg = 0;
		nextchar = 0;
	    }
	    else
	    {
		/*
		 * This is an option that requires an argument.
		 */
		if (*nextchar != 0)
		{
		    optarg = nextchar;
		    /*
		     * If we end this ARGV-element by taking the rest as
		     * an arg, we must advance to the next element now.
		     */
		    optind++;
		}
		else
		if (optind == argc)
		{
		    if (opterr != 0)
                        fprintf(stderr, "\n%s: no argument for `-%c' option\n",
				argv[0], c);
		    c = '?';
		}
		else
		    /*
		     * We already incremented `optind' once; increment it
		     * again when taking next ARGV-elt as argument.
		     */
		    optarg = argv[optind++];
		nextchar = 0;
	    }
	}
	return c;
    }
}


#if defined(TEST)

/*
 * Compile with -DTEST to make an executable for use in testing the above
 * definition of `getopt'.
 */

int
main(argc, argv)
int             argc;
char          **argv;
{
    char            c;
    int             digit_optind = 0;

    while (1)
    {
	int             this_option_optind = optind;
	if ((c = getopt(argc, argv, "abc:d:0123456789")) == EOF)
	    break;

	switch (c)
	{
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	    if (digit_optind != 0 && digit_optind != this_option_optind)
		printf("digits occur in two different argv-elements.\n");
	    digit_optind = this_option_optind;
	    printf("option %c\n", c);
	    break;

	case 'a':
	    printf("option a\n");
	    break;

	case 'b':
	    printf("option b\n");
	    break;

	case 'c':
	    printf("option c with value `%s'\n", optarg);
	    break;

	case '?':
	    break;

	default:
	    printf("?? getopt returned character code 0%o ??\n", c);
	}
    }

    if (optind < argc)
    {
	printf("non-option ARGV-elements: ");
	while (optind < argc)
	    printf("%s ", argv[optind++]);
	printf("\n");
    }

    return 0;
}

#endif
